/* Sentinel - IRC Statistical and Operator Services
** dcc.c - DCC handling
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: dcc.c,v 1.154 2004/05/23 01:32:19 wcampbel Exp $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "token.h"
#include "flags.h"
#include "event.h"
#include "match.h"
#include "s_string.h"
#include "slog.h"
#include "mem.h"

#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif

#define PERM_DENIED(conn, s)	{ slog(DEFAULT_LOG,L_ERR,"Access Denied (%s) to %s", (s), \
                                      (conn)->name); sendto_dcc((conn)->fd, \
                                                                "Access Denied."); \
  alert("%s Attempt by %s (failed)", (s), \
        (conn)->name); sendto_dcc_flag(CF_COMMANDS, \
                                       "%s Attempt by %s (failed)", (s), \
                                       (conn)->name); }

#define USER_TYPE(conn)		(((conn)->myuser != NULL) ? \
                          (IsSetOperAdmin((conn)->myuser) ? "Admin" : \
                           "Oper") : "User")

#ifdef ENABLE_TAKEOVER
dlink_list takeover_list;
#endif

static char errstr[BUFSIZE];

static int d_whom(struct Connection *, int, char **);
static int d_help(struct Connection *, int, char **);
static int d_login(struct Connection *, int, char **);
static int d_challenge(struct Connection *, int, char **);
static int d_echo(struct Connection *, int, char **);
static int d_flags(struct Connection *, int, char **);
static int d_rehash(struct Connection *, int, char **);
static int d_restart(struct Connection *, int, char **);
static int d_shutdown(struct Connection *, int, char **);
static int d_quit(struct Connection *, int, char **);
static int d_quote(struct Connection *, int, char **);
static int d_stats(struct Connection *, int, char **);
static int d_htmlupdate(struct Connection *, int, char **);
static int d_spforget(struct Connection *, int, char **);
static int d_spadd(struct Connection *, int, char **);
static int d_mapupdate(struct Connection *, int, char **);
static int d_kick(struct Connection *, int, char **);
static int d_me(struct Connection *, int, char **);
static int d_events(struct Connection *, int, char **);
static int d_ulist(struct Connection *, int, char **);
static int d_tlist(struct Connection *, int, char **);
static int d_nlist(struct Connection *, int, char **);
static int d_slist(struct Connection *, int, char **);
static int d_clist(struct Connection *, int, char **);
static int d_glist(struct Connection *, int, char **);
static int d_vlist(struct Connection *, int, char **);
static int d_vempty(struct Connection *, int, char **);
static int d_cmembers(struct Connection *, int, char **);
static int d_whois(struct Connection *, int, char **);
static int d_mem(struct Connection *, int, char **);
static int d_droneent(struct Connection *, int, char **);
static int d_opers(struct Connection *, int, char **);
static int d_logout(struct Connection *, int, char **);
static int d_op(struct Connection *, int, char **);
static int d_takeover(struct Connection *, int, char **);
static int d_release(struct Connection *, int, char **);
static int d_invite(struct Connection *, int, char **);

static void add_msg_to_hash_table(char *, struct MsgTable *);
static struct MsgTable *msg_hash[MSG_TABLE_SIZE];

static struct MsgTable dcc_table[] =
  {
    { ".whom", 0, 0, NULL, d_whom },
    { ".help", 0, 0, NULL, d_help },
    { ".login", 0, 0, NULL, d_login },
    { ".challenge", 0, 0, NULL, d_challenge },
    { ".echo", 0, 0, NULL, d_echo },
    { ".flags", 0, 0, NULL, d_flags },
    { ".rehash", 0, 0, NULL, d_rehash },
    { ".restart", 0, 0, NULL, d_restart },
    { ".die", 0, 0, NULL, d_shutdown },
    { ".shutdown", 0, 0, NULL, d_shutdown },
    { ".quit", 0, 0, NULL, d_quit },
    { ".quote", 0, 0, NULL, d_quote },
    { ".raw", 0, 0, NULL, d_quote },
    { ".stats", 0, 0, NULL, d_stats },
    { ".htmlupdate", 0, 0, NULL, d_htmlupdate },
    { ".spforget", 0, 0, NULL, d_spforget },
    { ".spadd", 0, 0, NULL, d_spadd },
    { ".mapupdate", 0, 0, NULL, d_mapupdate },
    { ".kick", 0, 0, NULL, d_kick },
    { ".me", 0, 0, NULL, d_me },
    { "\001ACTION", 0, 0, NULL, d_me },
    { ".events", 0, 0, NULL, d_events },
    { ".ulist", 0, 0, NULL, d_ulist },
    { ".tlist", 0, 0, NULL, d_tlist },
    { ".nlist", 0, 0, NULL, d_nlist },
    { ".slist", 0, 0, NULL, d_slist },
    { ".clist", 0, 0, NULL, d_clist },
    { ".glist", 0, 0, NULL, d_glist },
    { ".vlist", 0, 0, NULL, d_vlist },
    { ".vempty", 0, 0, NULL, d_vempty },
    { ".cmembers", 0, 0, NULL, d_cmembers },
    { ".whois", 0, 0, NULL, d_whois },
    { ".mem", 0, 0, NULL, d_mem },
    { ".droneent", 0, 0, NULL, d_droneent },
    { ".opers", 0, 0, NULL, d_opers },
    { ".logout", 0, 0, NULL, d_logout },
    { ".op", 0, 0, NULL, d_op },
    { ".takeover", 0, 0, NULL, d_takeover },
    { ".release", 0, 0, NULL, d_release },
    { ".invite", 0, 0, NULL, d_invite },
    { (char *)0, 0, 0, NULL, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { "whom", "help/dcc/whom", 0 },
    { "login", "help/dcc/login", 0 },
    { "challenge", "help/dcc/challenge", 0 },
    { "echo", "help/dcc/echo", 0 },
    { "flags", "help/dcc/flags", 1 },
    { "rehash", "help/dcc/rehash", 1 },
    { "restart", "help/dcc/restart", 1 },
    { "die", "help/dcc/shutdown", 1 },
    { "shutdown", "help/dcc/shutdown", 1 },
    { "quit", "help/dcc/quit", 0 },
    { "stats", "help/dcc/stats", 1 },
    { "htmlupdate", "help/dcc/htmlupdate", 1 },
    { "spforget", "help/dcc/spforget", 1 },
    { "spadd", "help/dcc/spadd", 1 },
    { "mapupdate", "help/dcc/mapupdate", 1 },
    { "kick", "help/dcc/kick", 1 },
    { "me", "help/dcc/me", 0 },
    { "events", "help/dcc/events", 1 },
    { "ulist", "help/dcc/ulist", 1 },
    { "tlist", "help/dcc/tlist", 1 },
    { "nlist", "help/dcc/nlist", 1 },
    { "slist", "help/dcc/slist", 1 },
    { "mem", "help/dcc/mem", 1 },
    { "clist", "help/dcc/clist", 1 },
    { "cmembers", "help/dcc/cmembers", 1 },
    { "glist", "help/dcc/glist", 1 },
    { "vlist", "help/dcc/vlist", 1 },
    { "whois", "help/dcc/whois", 1 },
    { "droneent", "help/dcc/droneent", 1 },
    { "opers", "help/dcc/opers", 1 },
    { "logout", "help/dcc/logout", 1 },
    { "op", "help/dcc/op", 1 },
    { "takeover", "help/dcc/takeover", 1 },
    { "release", "help/dcc/release", 1 },
    { "invite", "help/dcc/invite", 1 },
    { (char *)0, (char *)0, 0 }
  };

static void add_msg_to_hash_table(char *msg, struct MsgTable *m)
{
  m->hash = HASH((unsigned char *) msg, MSG_TABLE_SIZE);
  m->next = msg_hash[m->hash];
  msg_hash[m->hash] = m;
}

void InitDCCHash(void)
{
  int i;

  for (i = 0; dcc_table[i].handler != NULL; i++)
  {
    add_msg_to_hash_table(dcc_table[i].name, &dcc_table[i]);
  }
}

void dcc_parse(struct Connection *conn, char *line)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;
  static char buf[BUFSIZE];
  struct MsgTable *hashent;

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  strlcpy(buf, line, BUFSIZE);

  parc = msgtoken(line, parv);

  netinfo.requests++;

  if (parc == 0)
    return ;

  conn->last = CurrentTime;

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  /* Take the command through the hash table */
  hashent = msg_hash[HASH((unsigned char *) parv[0],
                          MSG_TABLE_SIZE)];
  while ((hashent != NULL) &&
         (strcasecmp(hashent->name, parv[0]) != 0))
    hashent = hashent->next;
  if (hashent != NULL)
  {
    status = hashent->handler(conn, parc, parv);
    if (status == 1)
      slog(DEFAULT_LOG, L_ERR, "Error with dcc command %s: %s", parv[0],
           (errstr) ? errstr : "No error message");
    return ;
  }

  if (parv[0][0] == '.')
    sendto_dcc(conn->fd, "Invalid command: %s", parv[0]);
  else
  {
    if (conn->flags & CF_CHAT)
      sendto_partyline(conn, "From %s: %s", conn->name, buf);
    else
      sendto_dcc(conn->fd, "You are not set +CHAT");
  }
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int d_help(struct Connection *conn, int parc, char *parv[])
{
  int i;

  spy(".help Request by %s for %s", conn->name,
      (parv[1]) ? parv[1] : "NO QUERY");
  sendto_dcc_flag(CF_COMMANDS, ".help Request by %s for %s",
                  conn->name, (parv[1]) ? parv[1] : "NO QUERY");
  if (parc < 2)
  {
    send_dcc_help_file(conn->fd, "help/dcc/index", "index");
    if (conn->myuser != NULL)
      send_dcc_help_file(conn->fd, "help/dcc/aindex",
                         "aindex");
    return 0;
  }

  /* Ignore any .'s at the beginning of the query */
  if (parv[1][0] == '.')
    parv[1]++;

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      if (help_table[i].myuonly)
      {
        if (conn->myuser)
          send_dcc_help_file(conn->fd,
                             help_table[i].filename,
                             help_table[i].name);
        else
          sendto_dcc(conn->fd,
                     "Permission Denied.");
      }
      else
      {
        send_dcc_help_file(conn->fd,
                           help_table[i].filename,
                           help_table[i].name);
      }

      return 0;
    }
  }

  sendto_dcc(conn->fd, "Unknown Help Topic: %s", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int d_echo(struct Connection *conn, int parc, char *parv[])
{
  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Echo is %s", (conn->flags & CONN_NOECHO) ?
               "OFF" : "ON");
    return 0;
  }
  if (strcasecmp(parv[1], "ON") == 0)
  {
    conn->flags &= ~CONN_NOECHO;
    sendto_dcc(conn->fd, "Echo is now ON");
    return 0;
  }
  else if (strcasecmp(parv[1], "OFF") == 0)
  {
    conn->flags |= CONN_NOECHO;
    sendto_dcc(conn->fd, "Echo is now OFF");
    return 0;
  }
  else
  {
    sendto_dcc(conn->fd, "Invalid option to .echo, please use ON "
               "or OFF");
    /* user error */
    return 0;
  }

  /* NOT REACHED */
  return 0;
}

static int d_whom(struct Connection *conn, int parc, char *parv[])
{
  struct Connection *tc;
  dlink_node *dl;
  int users = 0;

  DLINK_FOREACH(dl, connection_list.head)
  {
    tc = (struct Connection *) dl->data;
    if (tc->flags & CONN_CONNECTING)
      continue;
    if (tc->flags & (CONN_DCCIN | CONN_DCCOUT))
    {
      if (conn->myuser != NULL && IsSetOperAdmin(conn->myuser))
        sendto_dcc(conn->fd,
                   "%s: %s [%s](fd: %d)[%s] Idle: %s",
                   USER_TYPE(tc), tc->name,
                   (tc->myuser) ? tc->myuser->username : "*",
                   tc->fd, inet_ntoa(tc->sa->sin_addr),
                   timediff(CurrentTime - tc->last));
      else
        sendto_dcc(conn->fd, "%s: %s [%s](fd: %d) Idle: %s",
                   USER_TYPE(tc), tc->name,
                   (tc->myuser) ? tc->myuser->username : "*",
                   tc->fd, timediff(CurrentTime - tc->last));
    }
    users++;
  }

  if (users == 0)
  {
    /* If there's no users, how did we get this far? */
    strcpy(errstr, "No users in .whom");
    return 1;
  }

  return 0;
}

static int d_login(struct Connection *conn, int parc, char *parv[])
{
  struct MyUser *myu;
#if defined(CRYPT)

  char * crpass;
#endif

  if (parc < 3)
  {
    sendto_dcc(conn->fd,
               "Syntax: .login \37username\37 \37password\37");
    strcpy(errstr, "Missing parameters in .login");
    return 1;
  }

  myu = findmyuser(parv[1]);
  if (myu == NULL)
  {
    alert("Failed .login attempt (unknown user: %s)", parv[1]);
    sendto_dcc_flag(CF_AUTH,
                    "Failed .login attempt (unknown user: %s)", parv[1]);
    sendto_dcc(conn->fd, "Unknown User.");
    slog(DEFAULT_LOG, L_ERR, "Failed login attempt (unknown user (%s))", parv[1]);
    /* Not a code error */
    return 0;
  }

#ifdef HAVE_LIBCRYPTO
  if (myu->keyfile != NULL)
  {
    sendto_dcc(conn->fd, ".login disabled for your operator "
               "block.  Use .challenge.");
    return 0;
  }
#endif

  if (myu->password[0] == '\0')
  {
    sendto_dcc(conn->fd, "Invalid operator block");
    slog(DEFAULT_LOG, L_ERR, "Invalid operator block for %s", myu->username);
    return 0;
  }

#ifdef CRYPT
  crpass = crypt(parv[2], myu->password);
  if (!strcasecmp(myu->username, parv[1]) &&
      !strcmp(myu->password, crpass))
#else

  if (!strcasecmp(myu->username, parv[1]) &&
      !strcmp(myu->password, parv[2]))
#endif

  {
    slog(DEFAULT_LOG, L_NOTICE, "%s [%s][fd: %d] successfully logged in.",
         conn->name,
         myu->username, conn->fd);
    alert("Successful .login by %s to myunick %s", conn->name,
          myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "Successful .login by %s to myunick %s", conn->name,
                    myu->username);
    sendto_dcc(conn->fd, "Permission Granted.");
    sendto_all_dcc("%s [%s] successfully logged in.", conn->name,
                   myu->username);
    conn->myuser = myu;
    myu->flags |= MFLAG_DCC;
    myu->conn = conn;
    conn->flags |= settings.default_flags;
    sendto_dcc(conn->fd, "Your permissions are:%s",
               show_oper_flags(myu));
    send_set_flags(conn);
    send_unset_flags(conn);
    return 0;
  }
  else
  {
    slog(DEFAULT_LOG, L_ERR, "%s [%s][fd: %d] failed login.", conn->name,
         myu->username, conn->fd);
    alert("Failed .login attempt (invalid password) by %s on "
          "myuser nick %s", conn->name, myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "Failed .login attempt (invalid password) by %s on "
                    "myuser nick %s", conn->name, myu->username);
    sendto_dcc(conn->fd, "Access Denied.");
    /* not a code error */
    return 0;
  }

  /* NOT REACHED */
  return 0;
}

static int d_challenge(struct Connection *conn, int parc, char *parv[])
{
#ifdef HAVE_LIBCRYPTO
  struct MyUser *myu;
  char *challenge;

  if (parc < 2)
  {
    sendto_dcc(conn->fd,
               "Syntax: .challenge <nick> [response]");
    strcpy(errstr, "Missing parameters in .challenge");
    return 1;
  }

  myu = findmyuser(parv[1]);
  if (myu == NULL)
  {
    alert("Failed .challenge attempt (unknown user: %s)", parv[1]);
    sendto_dcc_flag(CF_AUTH,
                    "Failed .challenge attempt (unknown user: %s)", parv[1]);
    sendto_dcc(conn->fd, "Unknown User.");
    slog(DEFAULT_LOG, L_ERR, "Failed challenge attempt (unknown user (%s))",
         parv[1]);
    /* Not a code error */
    return 0;
  }

  /* parc == 2, create the challenge and store the response */
  if (myu->keyfile == NULL)
  {
    sendto_dcc(conn->fd, "Challenge is not enabled for your "
               "operator block.");
    return 0;
  }
  if (parc == 2)
  {
    if (generate_challenge(&challenge, &(myu->response),
                           myu->keyfile) == 0)
    {
      sendto_dcc(conn->fd, "Challenge: %s", challenge);
      return 0;
    }
    else
    {
      sendto_dcc(conn->fd, "Challenge error");
      return 0;
    }
  }
  if ((myu->response != NULL) && (strcmp(parv[2], myu->response) == 0))
  {
    free(myu->response);
    myu->response = NULL;
    slog(DEFAULT_LOG, L_NOTICE, "%s [%s][fd: %d] successfully logged in.",
         conn->name, myu->username, conn->fd);
    alert("Successful .challenge by %s to myunick %s", conn->name,
          myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "Successful .challenge by %s to myunick %s", conn->name,
                    myu->username);
    sendto_dcc(conn->fd, "Permission Granted.");
    sendto_all_dcc("%s [%s] successfully logged in.", conn->name,
                   myu->username);
    conn->myuser = myu;
    myu->flags |= MFLAG_DCC;
    myu->conn = conn;
    conn->flags |= settings.default_flags;
    sendto_dcc(conn->fd, "Your permissions are:%s",
               show_oper_flags(myu));
    send_set_flags(conn);
    send_unset_flags(conn);
    return 0;
  }
  else
  {
    /* XXX - Should it be free'ed? */
    slog(DEFAULT_LOG, L_ERR, "%s [%s][fd: %d] failed challenge.", conn->name,
         myu->username, conn->fd);
    alert("Failed .challenge attempt (incorrect response) by %s on "
          "myuser nick %s", conn->name, myu->username);
    sendto_dcc_flag(CF_AUTH,
                    "Failed .challenge attempt (incorrect response) by %s "
                    "on myuser nick %s", conn->name, myu->username);
    sendto_dcc(conn->fd, "Access Denied.");
    /* not a code error */
    return 0;
  }
  /* NOT REACHED */
  return 0;
#else

  sendto_dcc(conn->fd, ".challenge disabled.");
  return 0;
#endif
}

static int d_flags(struct Connection *conn, int parc, char *parv[])
{
  int i;
  int val;
  char *tok;
  int toadd;

  if (conn->myuser == NULL)
  {
    sendto_dcc(conn->fd, "You must .login to use .flags");
    return 0;
  }

  if (parc < 2)
  {
    send_set_flags(conn);
    send_unset_flags(conn);
    return 0;
  }
  for (i = 1; i < parc; i++)
  {
    if (!isalpha((int) parv[i][0]))
    {
      if (parv[i][0] == '+')
        toadd = 1;
      else if (parv[i][0] == '-')
        toadd = 0;
      else
      {
        sendto_dcc(conn->fd,
                   "Invalid type character: %c",
                   parv[i][0]);
        continue;
      }

      tok = parv[i] + 1;
    }
    else
    {
      toadd = 1;
      tok = parv[i];
    }
    val = token_to_value(flags_table, tok);
    if ((val != TOKEN_UNMATCHED) && (val != TOKEN_ERROR))
    {
      if (toadd)
        conn->flags |= val;
      else
        conn->flags &= ~val;
    }
    else
    {
      sendto_dcc(conn->fd, "Unknown flag: %s", tok);
    }
  }
  send_set_flags(conn);
  send_unset_flags(conn);
  return 0;
}

static int d_rehash(struct Connection *conn, int parc, char *parv[])
{
  if (conn->myuser == NULL || !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".rehash");
    return 0;
  }

  rehash();

  alert(".rehash by %s", conn->name);
  sendto_dcc_flag(CF_NOTICES, ".rehash by %s", conn->name);
  sendto_dcc(conn->fd, ".rehash complete");

  return 0;
}

static int d_op(struct Connection *conn, int parc, char *parv[])
{
#ifdef ENABLE_OP
  struct Channel *c;
  struct User *u;
  struct ChanUser *cu;
  int adminopped = FALSE;
  int member = FALSE; /* Is AdminServ already in the channel? */

  if (conn->myuser == NULL || !IsSetOperOp(conn->myuser))
  {
    PERM_DENIED(conn, ".op");
    return 0;
  }

  if (parc < 3)
  {
    sendto_dcc(conn->fd, "Syntax:  .op <channel> <nick>");
    return 0;
  }

  c = findchannel(parv[1]);
  if (c == NULL)
  {
    sendto_dcc(conn->fd, "Channel %s does not exist", parv[1]);
    return 0;
  }

  u = finduser(parv[2]);
  if (u == NULL)
  {
    sendto_dcc(conn->fd, "User %s does not exist", parv[2]);
    return 0;
  }

  cu = findchanuser(c, AdminServ.u);
  if (cu != NULL)
  {
    member = TRUE;
    if (cu->modes & CMODE_OP)
      adminopped = TRUE;
    else
      /* adminopped defaults to FALSE, we need to make it an op */
      cu->modes |= CMODE_OP;
  }

  cu = findchanuser(c, u);
  if (cu == NULL)
  {
    sendto_dcc(conn->fd, "User %s is not in channel %s", u->nick,
               c->name);
    return 0;
  }

  if ((member == TRUE) && (adminopped == FALSE))
  {
    sts(":%s PART %s :Preparing to .op", AdminServ.nick, c->name);
  }

  if ((member == FALSE) || ((member == TRUE) && (adminopped == FALSE)))
  {
    /* The logic here is to SJOIN into the channel with ops, set
    ** the user to opped, then part.  This logic is chosed as it's
    ** the only one that will work for all TSora servers.  Server
    ** MODE's or non-member MODE's have incompatabilities with certain
    ** servers.  Bahamut will work as well, as SJOIN'ing into a channel
    ** is not a U-lined function.
    */
    sts(":%s SJOIN %ld %s 0 :@%s", settings.name, c->ts, c->name,
        AdminServ.nick);
    /* I think I can avoid having to add the bot to the channel */
  }
  sts(":%s MODE %s +o %s", AdminServ.nick, c->name, u->nick);
  if (member == FALSE)
  {
    sts(":%s PART %s :Complete.", AdminServ.nick, c->name);
  }

  cu->modes |= CMODE_OP;

  slog(DEFAULT_LOG, L_WARNING, ".op %s %s by %s", c->name, u->nick, conn->name);
  alert(".op %s %s by %s", c->name, u->nick, conn->name);
#else

  sendto_dcc(conn->fd, ".op Disabled.");
#endif

  return 0;
}

static int d_invite(struct Connection *conn, int parc, char *parv[])
{
  struct Channel *c;
  struct ChanUser *cu;
  struct User *u;

  /* For now, I'm just allowing anyone logged in to be invited to
  ** any channel AdminServ is a member of.
  */
  if (conn->myuser == NULL)
  {
    PERM_DENIED(conn, ".invite");
    return 0;
  }

  if (parc < 3)
  {
    sendto_dcc(conn->fd, "Syntax:  .invite <nick> <channel>");
    return 0;
  }

  c = findchannel(parv[2]);
  if (c == NULL)
  {
    sendto_dcc(conn->fd, "Channel %s does not exist.", parv[2]);
    return 0;
  }

  u = finduser(parv[1]);
  if (u == NULL)
  {
    sendto_dcc(conn->fd, "User %s does not exist.", parv[1]);
    return 0;
  }

  cu = findchanuser(c, AdminServ.u);
  if (cu == NULL)
  {
    sendto_dcc(conn->fd, "%s is not a member of %s", AdminServ.nick, c->name);
    return 0;
  }
  if (! (cu->modes & CMODE_OP) )
  {
    sendto_dcc(conn->fd, "%s is not opped in %s", AdminServ.nick, c->name);
    return 0;
  }

  sts(":%s INVITE %s %s", AdminServ.nick, parv[1], parv[2]);
  return 0;
}

static int d_release(struct Connection *conn, int parc, char *parv[])
{
#ifdef ENABLE_TAKEOVER
  struct Channel *c;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperTakeover(conn->myuser))
  {
    PERM_DENIED(conn, ".release");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .release <channel>");
    return 0;
  }

  DLINK_FOREACH(dl, takeover_list.head)
  {
    if (irccasecmp(parv[1], (char *)dl->data) == 0)
      break;
  }
  if (dl == NULL)
  {
    sendto_dcc(conn->fd, "Cannot .release a channel not .takeover'ed");
    return 0;
  }

  c = findchannel(parv[1]);
  if (c == NULL)
  {
    slog(DEFAULT_LOG, L_ERR, "Release error, channel %s does not exist", parv[1]);
    return 0;
  }

  sts(":%s TOPIC %s :Channel has been released.", AdminServ.nick, c->name);
  DelUserFromChannel(c, AdminServ.u);
  sts(":%s PART %s :No reason", AdminServ.nick, c->name);

  dlink_delete(dl, &takeover_list);
  dlink_free(dl);
#else

  sendto_dcc(conn->fd, ".release Disabled.");
#endif

  return 0;
}

static int d_takeover(struct Connection *conn, int parc, char *parv[])
{
#ifdef ENABLE_TAKEOVER
  struct Channel *c;
  struct ChanUser *cu;
  char reason[BUFSIZE];
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperTakeover(conn->myuser))
  {
    PERM_DENIED(conn, ".takeover");
    return 0;
  }

  if (parc < 3)
  {
    sendto_dcc(conn->fd, "Syntax:  .takeover <channel> <reason>");
    return 0;
  }

  c = findchannel(parv[1]);
  if (c == NULL)
  {
    sendto_dcc(conn->fd, "Channel %s does not exist.", parv[1]);
    return 0;
  }

  if (c->ts == 0)
  {
    sendto_dcc(conn->fd, "Cannot takeover TS0 channels.");
    return 0;
  }

  if (c->ts == 1)
  {
    sendto_dcc(conn->fd, "Cannot takeover TS1 channels.");
    return 0;
  }

  DLINK_FOREACH(dl, takeover_list.head)
  {
    if (irccasecmp(c->name, (char *)dl->data) == 0)
    {
      sendto_dcc(conn->fd, "Channel %s already taken over.", c->name);
      return 0;
    }
  }

  ExpandParv(reason, BUFSIZE, 2, parc, parv);

  cu = findchanuser(c, AdminServ.u);
  if (cu != NULL)
  {
    DelUserFromChannel(c, AdminServ.u);
    sts(":%s PART %s :No reason", AdminServ.nick, c->name);
  }

  /* In this case, we SJOIN into the channel with the currentTS - 1, so
  ** that it is taken over.
  ** We retain ops.
  */
#if defined(IRCD_HYBRID) || defined(IRCD_HYB6) || defined(IRCD_HYB7) || \
    defined(IRCD_BAHAMUT) || defined(IRCD_HYBSVS)
  sts(":%s SJOIN %ld %s +ntmi :@%s", settings.name, c->ts - 1, c->name,
      AdminServ.nick);
  HandleTSChange(c, c->ts - 1);
#endif

  /* XXX - Is there a s-s format that is preferred? */
  sts(":%s TOPIC %s %s %ld :[TakeOver] %s", settings.name, c->name,
      AdminServ.nick, CurrentTime, reason);

  AddUserFlagToChannel(c, AdminServ.u, CMODE_OP);

  dl = dlink_create();
  dlink_add(sstrdup(c->name), dl, &takeover_list);

  slog(DEFAULT_LOG, L_WARNING, ".takeover %s [%s] by %s", c->name, reason,
       conn->name);
  alert(".takeover %s %s by %s", c->name, reason, conn->name);
#else

  sendto_dcc(conn->fd, ".takeover Disabled.");
#endif

  return 0;
}

static int d_restart(struct Connection *conn, int parc, char *parv[])
{
  char comm[BUFSIZE];

  if (conn->myuser == NULL || !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".restart");
    return 0;
  }

  comm[0] = '\0';

  ExpandParv(comm, BUFSIZE, 1, parc, parv);

  wallops(settings.name, "%s requested RESTART: %s", conn->name,
          (comm[0]) ? comm : "No Reason");
  alert("Service Restart by %s: %s", conn->name,
        (comm[0]) ? comm : "No Reason");
  sendto_dcc_flag(CF_NOTICES, "Service Restart by %s: %s",
                  conn->name, (comm[0]) ? comm : "No Reason");
#ifdef STATSERV

  sts(":%s QUIT :Restart Request by %s: %s", StatServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif

  sts(":%s QUIT :Restart Request by %s: %s", AdminServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#ifdef SPLITSERV

  sts(":%s QUIT :Restart Request by %s: %s", SplitServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif
#ifdef JUPESERV

  sts(":%s QUIT :Restart Request by %s: %s", JupeServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif
#ifdef LISTSERV

  sts(":%s QUIT :Restart Request by %s: %s", ListServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif
#ifdef MONSERV

  sts(":%s QUIT :Restart Request by %s: %s", MonServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif

  sleep(1);
  shutdown(servsock, SHUT_RDWR);
  close(servsock);
  slog(DEFAULT_LOG, L_ALERT, "%s [%s](fd %d) requested .restart: %s", conn->name,
       conn->myuser->username, conn->fd, (comm) ? comm : "No Reason");
  close_dccs();
  SaveStats();
  execv(SPATH, NULL);

  /* NOT REACHED */
  return 0;
}

static int d_shutdown(struct Connection *conn, int parc, char *parv[])
{
  char comm[BUFSIZE];

  if (conn->myuser == NULL || !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".shutdown");
    return 0;
  }

  comm[0] = '\0';

  ExpandParv(comm, BUFSIZE, 1, parc, parv);

  wallops(settings.name, "%s requested SHUTDOWN: %s", conn->name,
          (comm[0]) ? comm : "No Reason");
  alert("Service Shutdown by %s: %s", conn->name,
        (comm[0]) ? comm : "No Reason");
  sendto_dcc_flag(CF_NOTICES, "Service Shutdown by %s: %s", conn->name,
                  (comm[0]) ? comm : "No Reason");
#ifdef STATSERV

  sts(":%s QUIT :Shutdown Request by %s: %s", StatServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif

  sts(":%s QUIT :Shutdown Request by %s: %s", AdminServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#ifdef SPLITSERV

  sts(":%s QUIT :Shutdown Request by %s: %s", SplitServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif
#ifdef JUPESERV

  sts(":%s QUIT :Shutdown Request by %s: %s", JupeServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif
#ifdef LISTSERV

  sts(":%s QUIT :Shutdown Request by %s: %s", ListServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif
#ifdef MONSERV

  sts(":%s QUIT :Shutdown Request by %s: %s", MonServ.nick, conn->name,
      (comm[0]) ? comm : "No Reason");
#endif

  sleep(1);
  shutdown(servsock, SHUT_RDWR);
  close(servsock);
  slog(DEFAULT_LOG, L_ALERT, "%s [%s](fd %d) requested .shutdown: %s", conn->name,
       conn->myuser->username, conn->fd, (comm) ? comm : "No Reason");
  close_dccs();
  SaveStats();
  close_logfiles();
  exit(0);

  /* NOT REACHED */
  return 0;
}

static int d_quit(struct Connection *conn, int parc, char *parv[])
{
  sendto_all_dcc("Client %s disconnected", conn->name);
  shutdown(conn->fd, SHUT_RDWR);
  close(conn->fd);
  conn->flags |= CONN_DEAD;
  dying_connection(conn->fd);
  return 0;
}

static int d_quote(struct Connection *conn, int parc, char *parv[])
{
#ifdef ENABLE_QUOTE
  char command[BUFSIZE];

  if (conn->myuser == NULL || !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".quote");
    return 0;
  }

  ExpandParv(command, BUFSIZE, 1, parc, parv);

  if (command[0] == '\0')
  {
    sendto_dcc(conn->fd, "Please enter a valid command");
    strcpy(errstr, "NULL command attempt in QUOTE");
    return 1;
  }

  sts("%s", command);
  slog(DEFAULT_LOG, L_WARNING, "%s (FD: %d) made me execute %s", conn->name, conn->fd,
       command);
  return 0;
#else

  sendto_dcc(conn->fd, ".quote/.raw Disabled.");
  return 0;
#endif
}

static int d_stats(struct Connection *conn, int parc, char *parv[])
{
#ifdef STATSERV
  struct Stats *st;

  if (conn->myuser == NULL || !IsSetOperStats(conn->myuser))
  {
    PERM_DENIED(conn, ".stats");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax: .stats [del|list|copy]");
    strcpy(errstr, "Missing parameter in .stats");
    return 1;
  }

  if (!strcasecmp(parv[1], "LIST"))
  {
    int i = 1;
    dlink_node *dl;
    spy(".stats list Request by %s", conn->name);
    sendto_dcc_flag(CF_COMMANDS, ".stats list Request by %s",
                    conn->name);
    sendto_dcc(conn->fd, "Statistics Database:");
    DLINK_FOREACH(dl, shead.head)
    {
      st = (struct Stats *) dl->data;
      sendto_dcc(conn->fd, "[%-2d] %s", i, st->name);
      i++;
    }
    sendto_dcc(conn->fd, "End of List.");
    slog(DEFAULT_LOG, L_INFO, "%s requested .stats list", conn->name);
  }
  else if (!strcasecmp(parv[1], "DEL"))
  {
    dlink_node *dl;

    if (parc < 3)
    {
      sendto_dcc(conn->fd, "Syntax: .stats del <name>");
      strcpy(errstr, "Missing parameter in .stats");
      return 1;
    }
    st = findstats(parv[2]);
    if (st == NULL)
    {
      spy(".stats del %s Attempt by %s (failed)",
          parv[2], conn->name);
      sendto_dcc_flag(CF_COMMANDS,
                      ".stats del %s Attempt by %s (failed)",
                      parv[2], conn->name);
      sendto_dcc(conn->fd, "%s is not in the database!",
                 parv[2]);
      snprintf(errstr, BUFSIZE, "Unknown server: %s",
               parv[2]);
      return 1;
    }
    DLINK_FOREACH(dl, shead.head)
    {
      st = (struct Stats *) dl->data;
      if (!strcasecmp(parv[2], st->name))
      {
        dlink_delete(dl, &shead);
        dlink_free(dl);
        memcounts.stats--;
        free(st);
        break;
      }
    }
    spy(".stats del %s Request by %s", parv[2], conn->name);
    sendto_dcc_flag(CF_COMMANDS, ".stats del %s Request by %s",
                    parv[2], conn->name);
    sendto_dcc(conn->fd, "Removed %s from the database.", parv[2]);
    slog(DEFAULT_LOG, L_WARNING, "%s requested .stats del %s", conn->name,
         parv[2]);
  }
  else if (!strcasecmp(parv[1], "COPY"))
  {
    struct Server *s;
    struct Stats *st2;
    dlink_node *dl;

    if (parc < 4)
    {
      sendto_dcc(conn->fd, "Syntax: .stats copy <name> "
                 "<newname>");
      strcpy(errstr, "Missing parameter in STATS COPY");
      return 1;
    }
    st = findstats(parv[2]);
    if (st == NULL)
    {
      sendto_dcc(conn->fd, "No entry in the database for %s",
                 parv[2]);
      snprintf(errstr, BUFSIZE, "Unknown stats entry: %s",
               parv[2]);
      return 1;
    }
    s = findserver(parv[2]);
    if (s != NULL)
    {
      sendto_dcc(conn->fd, "Server %s is online!", parv[2]);
      snprintf(errstr, BUFSIZE, "Server %s is online",
               parv[2]);
      return 1;
    }

    /* Wait until we are sure before removing a statistics
    ** entry
    */
    DLINK_FOREACH(dl, shead.head)
    {
      st2 = (struct Stats *) dl->data;
      if (!strcasecmp(parv[3], st2->name))
      {
        dlink_delete(dl, &shead);
        dlink_free(dl);
        memcounts.stats--;
        free(st2);
        break;
      }
    }
    s = NULL;
    strlcpy(st->name, parv[3], sizeof(st->name));
    s = findserver(st->name);
    if (s != NULL)
      s->stats = st;
    sendto_dcc(conn->fd, "Moved database entry for %s to %s",
               parv[2], parv[3]);
    slog(DEFAULT_LOG, L_WARNING, "%s requested .stats copy %s -> %s", conn->name,
         parv[2], parv[3]);
    spy(".stats copy %s %s Request by %s", parv[2], parv[3],
        conn->name);
    sendto_dcc_flag(CF_COMMANDS, ".stats copy %s %s Request by %s",
                    parv[2], parv[3], conn->name);
  }
  else
  {
    sendto_dcc(conn->fd, "Invalid Argument.");
    strcpy(errstr, "Invalid argument in STATS");
    return 1;
  }

  SaveStats();
  return 0;
#else

  sendto_dcc(conn->fd, ".stats Disabled.");
  return 0;
#endif
}

static int d_htmlupdate(struct Connection *conn, int parc, char *parv[])
{
#ifdef STATSERV
  if (conn->myuser == NULL || !IsSetOperStats(conn->myuser))
  {
    PERM_DENIED(conn, ".htmlupdate");
    return 0;
  }

  if (!IsHtml())
  {
    sendto_dcc(conn->fd, "HTML Update Disabled");
    return 0;
  }

  DumpHTML();
  spy(".htmlupdate Request by %s", conn->name);
  sendto_dcc_flag(CF_COMMANDS, ".htmlupdate Request by %s", conn->name);
  sendto_dcc(conn->fd, "HTML Update Forced");
  slog(DEFAULT_LOG, L_NOTICE, "%s@fd:%d forces an HTML Update", conn->name, conn->fd);
  return 0;
#else

  sendto_dcc(conn->fd, ".htmlupdate Disabled.");
  return 0;
#endif
}

static int d_spforget(struct Connection *conn, int parc, char *parv[])
{
#ifdef SPLITSERV
  struct SpServer *s;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperSplit(conn->myuser))
  {
    PERM_DENIED(conn, ".spforget");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax: .spforget <server>");
    strcpy(errstr, "Missing parameter in .spforget");
    return 1;
  }

  DLINK_FOREACH(dl, split_table.head)
  {
    s = (struct SpServer *) dl->data;
    if (!strcasecmp(s->name, parv[1]))
    {
      sendto_dcc(conn->fd, "Removed from the Split Table",
                 s->name);
      dlink_delete(dl, &split_table);
      if (IsSPReport())
        notice(sp.join_chan, SplitServ.nick, "FORGOT SPLIT: %s", s->name);
      slog(SPLIT_LOG, L_NOTICE, "Forgot split %s by %s", s->name, conn->name);
      free(s);
      memcounts.spserver--;
      dlink_free(dl);
      return 0;
    }
  }

  sendto_dcc(conn->fd, "%s NOT found in the Split Table", parv[1]);
#else

  sendto_dcc(conn->fd, ".spforget Disabled.");
#endif

  return 0;
}

static int d_spadd(struct Connection *conn, int parc, char *parv[])
{
#ifdef SPLITSERV
  struct SpServer *s = NULL;

  if (conn->myuser == NULL || !IsSetOperSplit(conn->myuser))
  {
    PERM_DENIED(conn, ".spadd");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax: .spadd <server>");
    strcpy(errstr, "Missing parameter in .spadd");
    return 1;
  }

  s = SpFindServer(parv[1]);
  if (s != NULL)
  {
    sendto_dcc(conn->fd,
               "Can't add a split for an existing server");
    return 0;
  }
  s = SpFindSplit(parv[1]);
  if (s != NULL)
  {
    sendto_dcc(conn->fd, "Can't add a split for an existing split");
    return 0;
  }
  SpNewSplit(parv[1]);
  if (IsSPReport())
    notice(sp.join_chan, SplitServ.nick, "ADDED SPLIT: %s", parv[1]);
  slog(SPLIT_LOG, L_NOTICE, "Added split %s by %s", parv[1], conn->name);

#else

  sendto_dcc(conn->fd, ".spadd Disabled.");
#endif

  return 0;
}

static int d_mapupdate(struct Connection *conn, int parc, char *parv[])
{
#ifdef SPLITSERV
  if (conn->myuser == NULL || !IsSetOperSplit(conn->myuser))
  {
    PERM_DENIED(conn, ".mapupdate");
    return 0;
  }

  if (IsSPEnableText())
  {
    dump_map();
    sendto_dcc(conn->fd, "MAP file updated");
    return 0;
  }
  sendto_dcc(conn->fd, "MAP Text Dump Disabled");
#else

  sendto_dcc(conn->fd, ".mapupdate Disabled.");
#endif

  return 0;
}

static int d_kick(struct Connection *conn, int parc, char *parv[])
{
  struct Connection *target;
  int tfd = 0;

  if (conn->myuser == NULL)
  {
    PERM_DENIED(conn, ".kick");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax: .kick <fd>");
    strcpy(errstr, "Missing parameter in .kick");
    return 1;
  }

  tfd = atoi(parv[1]);
  if (tfd <= 0)
  {
    sendto_dcc(conn->fd, "Syntax: .kick <fd>");
    strcpy(errstr, "Non-FD parameter in .kick");
    return 1;
  }
  target = find_connection(tfd);
  if (target == NULL)
  {
    sendto_dcc(conn->fd, "No such FD: %s", parv[1]);
    strcpy(errstr, "Invalid FD in .kick");
    return 1;
  }
  if (target->myuser != NULL && !IsSetOperAdmin(conn->myuser))
  {
    sendto_dcc(conn->fd, "Cannot kick another logged in user");
    slog(DEFAULT_LOG, L_ERR, ".kick %d (%s) Attempt by %s (failed)", target->fd,
         target->name, conn->name);
    spy(".kick %d (%s) Attempt by %s (failed)", target->fd,
        target->name, conn->name);
    sendto_dcc_flag(CF_COMMANDS,
                    ".kick %d (%s) Attempt by %s (failed)", target->fd,
                    target->name, conn->name);
    /* Not a code error */
    return 0;
  }
  /* FINALLY we can remove the client. */
  sendto_dcc(target->fd, "You have been kicked by %s", conn->name);
  sendto_dcc(conn->fd, "Kicking %s", target->name);
  shutdown(target->fd, SHUT_RDWR);
  close(target->fd);
  target->flags |= CONN_DEAD;
  sendto_all_dcc("%s has been kicked by %s", target->name, conn->name);
  delete_connection(target->fd);

  return 0;
}

static int d_me(struct Connection *conn, int parc, char *parv[])
{
  char line[BUFSIZE];
  char *ptr;

  ExpandParv(line, BUFSIZE, 1, parc, parv);

  if (line[0] == '\0')
    /* Ignore blank .me's */
    return 0;

  /* If this was caught be someone doing ^AACTION blah blah^A in their
  ** client, trim the extra ^A
  */
  ptr = strchr(line, '\001');
  if (ptr != NULL)
    *ptr = '\0';

  if (conn->flags & CF_CHAT)
    sendto_partyline(conn, "* %s %s", conn->name, line);
  else
    sendto_dcc(conn->fd, "You are not set +CHAT");

  return 0;
}

static int d_logout(struct Connection *conn, int parc, char *parv[])
{
  dlink_node *dl;

  if (conn->myuser == NULL)
  {
    sendto_dcc(conn->fd, "Not logged in.");
    return 0;
  }

  conn->myuser->flags &= ~MFLAG_DCC;
  if (IsOperDying(conn->myuser) && !IsOperOn(conn->myuser))
  {
    dl = dlink_find_delete(conn->myuser, &myudying);
    dlink_free(dl);
#ifdef HAVE_LIBCRYPTO

    if (conn->myuser->response != NULL)
      free(conn->myuser->response);
    if (conn->myuser->keyfile != NULL)
      RSA_free(conn->myuser->keyfile);
#endif

    free(conn->myuser);
    memcounts.myuser--;
  }
  conn->myuser = NULL;
  sendto_dcc(conn->fd, "Logged out.");
  return 0;
}

static int d_events(struct Connection *conn, int parc, char *parv[])
{
  struct ev_enum *ev;
  const char *tmpe;

  if (conn->myuser == NULL || !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".events");
    return 0;
  }

  tmpe = get_last_event_ran();
  if (tmpe != NULL)
    sendto_dcc(conn->fd, "*** Last event to run: %s", tmpe);

  sendto_dcc(conn->fd, "*** Operation            Next Execution");

  start_enumeration();
  ev = enumerate_events();
  while (ev != NULL)
  {
    sendto_dcc(conn->fd, "*** %-20s %-3d seconds", ev->name, ev->next);
    ev = enumerate_events();
  }

  sendto_dcc(conn->fd, "*** Finished");
  return 0;
}

static int d_opers(struct Connection *conn, int parc, char *parv[])
{
  dlink_node *dl;
  struct MyUser *myu;

  if (conn->myuser == NULL)
  {
    PERM_DENIED(conn, ".opers");
    return 0;
  }
  if (IsOpersAdminOnly() && !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".opers");
    return 0;
  }

  DLINK_FOREACH(dl, myuhead.head)
  {
    myu = (struct MyUser *) dl->data;
    /* Space purposefully left out */
    sendto_dcc(conn->fd, "Operator %s flags:%s",
               myu->username, show_oper_flags(myu));
  }
  sendto_dcc(conn->fd, "End of List.");
  return 0;
}

static int d_ulist(struct Connection *conn, int parc, char *parv[])
{
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  char *username;
  char *hostname;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".ulist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .ulist user@hostmask [limit]");
    strcpy(errstr, "Not enough parameters for .ulist");
    return 1;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  username = parv[1];
  hostname = strchr(username, '@');
  if (hostname == NULL)
  {
    sendto_dcc(conn->fd,
               "Error:  Please use an @ in the .ulist mask");
    return 0;
  }
  *hostname = '\0';
  hostname++;

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] ULIST %s@%s %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], hostname, lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY ULIST %s@%s %d :(%s@fd:%d)", AdminServ.nick,
      parv[1], hostname, lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY ULIST %s@%s %d (%s@fd:%d)",
            parv[1], hostname, lim, conn->name, conn->fd);

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".ulist limit reached.");
        return 0;
      }
      if ((match(username, u->username) == 0) &&
          (match(hostname, u->hostname) == 0))
      {
        count++;
        sendto_dcc(conn->fd,
                   "User %d: %s (%s@%s) [%s]", count,
                   u->nick, u->username, u->hostname,
                   u->server->name);
      }
    }
  }
  sendto_dcc(conn->fd, "End of .ulist.");
  return 0;
}

static int d_tlist(struct Connection *conn, int parc, char *parv[])
{
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  struct TLD *t;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".tlist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .tlist tld [limit]");
    strcpy(errstr, "Not enough parameters for .tlist");
    return 1;
  }

  if (*parv[1] == '*')
    parv[1]++;
  if (*parv[1] == '.')
    parv[1]++;

  t = findtld(parv[1]);

  if (t == NULL)
  {
    sendto_dcc(conn->fd, "Unknown TLD: %s", parv[1]);
    return 0;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] TLIST %s %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY TLIST %s %d :(%s@fd:%d)", AdminServ.nick,
      parv[1], lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY TLIST %s %d (%s@fd:%d)",
            parv[1], lim, conn->name, conn->fd);

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".tlist limit reached.");
        return 0;
      }
      if (u->tld == t)
      {
        count++;
        sendto_dcc(conn->fd,
                   "User %d: %s (%s@%s) [%s]", count,
                   u->nick, u->username, u->hostname,
                   u->server->name);
      }
    }
  }
  sendto_dcc(conn->fd, "End of .tlist.");
  return 0;
}

static int d_nlist(struct Connection *conn, int parc, char *parv[])
{
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".nlist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .nlist nickmask [limit]");
    strcpy(errstr, "Not enough parameters for .nlist");
    return 1;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] NLIST %s %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY NLIST %s %d :(%s@fd:%d)", AdminServ.nick,
      parv[1], lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY NLIST %s %d (%s@fd:%d)",
            parv[1], lim, conn->name, conn->fd);

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".nlist limit reached.");
        return 0;
      }
      if (match(parv[1], u->nick) == 0)
      {
        count++;
        sendto_dcc(conn->fd,
                   "User %d: %s (%s@%s) [%s]", count,
                   u->nick, u->username, u->hostname,
                   u->server->name);
      }
    }
  }
  sendto_dcc(conn->fd, "End of .nlist.");
  return 0;
}

static int d_glist(struct Connection *conn, int parc, char *parv[])
{
#ifdef TRACK_GECOS
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".glist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .glist gecosmask [limit]");
    strcpy(errstr, "Not enough parameters for .glist");
    return 1;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] GLIST %s %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY GLIST %s %d :(%s@fd:%d)", AdminServ.nick,
      parv[1], lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY GLIST %s %d (%s@fd:%d)",
            parv[1], lim, conn->name, conn->fd);

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".glist limit reached.");
        return 0;
      }
      if (u->gecos == NULL)
        continue;
      if (match(parv[1], u->gecos) == 0)
      {
        count++;
        sendto_dcc(conn->fd,
                   "User %d: %s (%s@%s) [%s] [%s]", count,
                   u->nick, u->username, u->hostname,
                   u->server->name, u->gecos);
      }
    }
  }
  sendto_dcc(conn->fd, "End of .glist.");
  return 0;
#else

  sendto_dcc(conn->fd, ".glist Disabled.");
  return 0;
#endif
}

static int d_vlist(struct Connection *conn, int parc, char *parv[])
{
#ifdef TRACK_CTCPVERSION
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".vlist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .vlist ctcpversionmask [limit]");
    strcpy(errstr, "Not enough parameters for .vlist");
    return 1;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] VLIST %s %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY VLIST %s %d :(%s@fd:%d)", AdminServ.nick,
      parv[1], lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY VLIST %s %d (%s@fd:%d)",
            parv[1], lim, conn->name, conn->fd);

  if (delayed_users.count > 0)
  {
    sendto_dcc(conn->fd, "Warning:  %d clients still pending CTCP VERSION"
               " check, list not complete.", delayed_users.count);
  }

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".vlist limit reached.");
        return 0;
      }
      if (u->version == NULL)
        continue;
      if (match(parv[1], u->version) == 0)
      {
        count++;
        sendto_dcc(conn->fd,
                   "User %d: %s (%s@%s) [%s] [%s]", count,
                   u->nick, u->username, u->hostname,
                   u->server->name, u->version);
      }
    }
  }
  sendto_dcc(conn->fd, "End of .vlist.");
  return 0;
#else

  sendto_dcc(conn->fd, ".vlist Disabled.");
  return 0;
#endif
}

static int d_vempty(struct Connection *conn, int parc, char *parv[])
{
#ifdef TRACK_CTCPVERSION
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".vempty");
    return 0;
  }

  if (delayed_users.count > 0)
  {
    sendto_dcc(conn->fd, "%d clients still pending CTCP VERSION check, "
               "please wait a few minutes.", delayed_users.count);
    return 0;
  }

  if (parc >= 2)
  {
    lim = atoi(parv[1]);
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] VEMPTY %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY VEMPTY %d :(%s@fd:%d)", AdminServ.nick,
      lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY VEMPTY %d (%s@fd:%d)",
            parv[1], conn->name, conn->fd);

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".vempty limit reached.");
        return 0;
      }
      if (u->version == NULL)
      {
        count++;
        sendto_dcc(conn->fd, "User %d: %s (%s@%s) [%s]", count, u->nick,
                   u->username, u->hostname, u->server->name);
      }
    }
  }
  sendto_dcc(conn->fd, "End of .vempty.");
#else

  sendto_dcc(conn->fd, ".vempty Disabled.");
#endif

  return 0;
}

static int d_cmembers(struct Connection *conn, int parc, char *parv[])
{
  dlink_node *dl;
  struct Channel *c;
  struct ChanUser *cu;
  int count = 0;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".cmembers");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .cmembers channel");
    strcpy(errstr, "Not enough parameters for .cmembers");
    return 1;
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] CMEMBERS %s", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1]);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY CMEMBERS %s :(%s@fd:%d)", AdminServ.nick, parv[1],
      conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY CMEMBERS %s (%s@fd:%d)",
            parv[1], conn->name, conn->fd);

  c = findchannel(parv[1]);

  if (c == NULL)
  {
    sendto_dcc(conn->fd, "Channel %s does not exist", parv[1]);
    return 0;
  }

  sendto_dcc(conn->fd, "Members of %s:", c->name);
  DLINK_FOREACH(dl, c->members.head)
  {
    cu = (struct ChanUser *) dl->data;
    count++;
    sendto_dcc(conn->fd, "%d: %s%s (%s@%s)", count, CUModeString(cu),
               cu->user->nick, cu->user->username, cu->user->hostname);
  }

  return 0;
}

static int d_whois(struct Connection *conn, int parc, char *parv[])
{
  dlink_node *dl;
  struct ChanUser *cu;
  struct User *u;
  int count = 0;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".whois");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .whois nick");
    strcpy(errstr, "Not enough parameters for .whois");
    return 1;
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] WHOIS %s", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1]);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY WHOIS %s :(%s@fd:%d)", AdminServ.nick, parv[1],
      conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY WHOIS %s :(%s@fd:%d)",
            parv[1], conn->name, conn->fd);

  u = finduser(parv[1]);

  if (u == NULL)
  {
    sendto_dcc(conn->fd, "No such user");
    return 0;
  }

  sendto_dcc(conn->fd, "%s (%s@%s) on %s [TLD: %s]", u->nick, u->username,
             u->hostname, u->server->name, u->tld->tld);
#ifdef TRACK_CTCPVERSION

  sendto_dcc(conn->fd, "Version Reply:  %s", (u->version) ? u->version : "NONE");
#endif

  DLINK_FOREACH(dl, u->channels.head)
  {
    cu = (struct ChanUser *) dl->data;
    count++;
    sendto_dcc(conn->fd, "%d: %s%s", count, CUModeString(cu),
               cu->chan->name);
  }
  if (count == 0)
    sendto_dcc(conn->fd, "User is not in any channel");

  return 0;
}

static int d_clist(struct Connection *conn, int parc, char *parv[])
{
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct Channel *c;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".clist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd, "Syntax:  .clist chanmask [limit]");
    strcpy(errstr, "Not enough parameters for .clist");
    return 1;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] CLIST %s %d", conn->name,
       inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], lim);
#ifdef USE_OPERSPY

  sts(":%s ENCAP * OPERSPY CLIST %s %d :(%s@fd:%d)", AdminServ.nick,
      parv[1], lim, conn->name, conn->fd);
#endif

  if (IsSpyWallops())
    wallops(AdminServ.nick, "OPERSPY CLIST %s %d (%s@fd:%d)",
            parv[1], lim, conn->name, conn->fd);

  for (i = 0; i < C_TABLE_SIZE; i++)
  {
    for (c = chanlist[i]; c != NULL; c = c->next)
    {
      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".clist limit reached.");
        return 0;
      }
      if (match(parv[1], c->name) == 0)
      {
        count++;
        sendto_dcc(conn->fd,
                   "Channel %d: %s (TS: %d)(users: %d) [%s]",
                   count, c->name, c->ts, c->nummembers,
                   ModeString(c));
      }
    }
  }
  sendto_dcc(conn->fd, "End of .clist.");
  return 0;
}

static int d_slist(struct Connection *conn, int parc, char *parv[])
{
  int i;
  int lim = settings.defaultlimit;
  int count = 0;
  struct User *u;
  char *username = NULL;
  char *hostname = NULL;
  int limit_host = FALSE;
  dlink_node *dl;

  if (conn->myuser == NULL || !IsSetOperList(conn->myuser))
  {
    PERM_DENIED(conn, ".slist");
    return 0;
  }

  if (parc < 2)
  {
    sendto_dcc(conn->fd,
               "Syntax:  .slist server [limit] [user@hostmask]");
    strcpy(errstr, "Not enough parameters for .slist");
    return 1;
  }

  if (parc >= 3)
  {
    lim = atoi(parv[2]);
  }

  if (parc >= 4)
  {
    username = parv[3];
    hostname = strchr(username, '@');
    if (hostname == NULL)
    {
      sendto_dcc(conn->fd,
                 "Error:  Please use an @ in the .slist mask");
      return 0;
    }
    *hostname = '\0';
    hostname++;
    limit_host = TRUE;
  }

  if (limit_host)
  {
    slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] SLIST %s %d %s@%s", conn->name,
         inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1],
         lim, username, hostname);
#ifdef USE_OPERSPY

    sts(":%s ENCAP * OPERSPY SLIST %s %d %s@%s :(%s@fd:%d)", AdminServ.nick,
        parv[1], lim, username, hostname, conn->name, conn->fd);
#endif

    if (IsSpyWallops())
      wallops(AdminServ.nick, "OPERSPY SLIST %s %d %s@%s (%s@fd:%d)",
              parv[1], lim, username, hostname, conn->name, conn->fd);
  }
  else
  {
    slog(SPY_LOG, L_NOTICE, "%s[%s][FD: %d] SLIST %s %d *@*", conn->name,
         inet_ntoa(conn->sa->sin_addr), conn->fd, parv[1], lim);
#ifdef USE_OPERSPY

    sts(":%s ENCAP * OPERSPY SLIST %s %d *@* :(%s@fd:%d)", AdminServ.nick,
        parv[1], lim, conn->name, conn->fd);
#endif

    if (IsSpyWallops())
      wallops(AdminServ.nick, "OPERSPY SLIST %s %d *@* (%s@fd:%d)",
              parv[1], lim, conn->name, conn->fd);
  }

  for (i = 0; i < U_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, userhash[i].head)
    {
      u = dl->data;

      if ((lim > 0) && (count == lim))
      {
        sendto_dcc(conn->fd, ".slist limit reached.");
        return 0;
      }
      if (match(parv[1], u->server->name) == 0)
      {
        if (limit_host)
        {
          if ((match(username, u->username) == 0) &&
              (match(hostname, u->hostname) == 0))
          {
            count++;
            sendto_dcc(conn->fd,
                       "User %d: %s (%s@%s) [%s]", count,
                       u->nick, u->username, u->hostname,
                       u->server->name);
          }
        }
        else
        {
          count++;
          sendto_dcc(conn->fd,
                     "User %d: %s (%s@%s) [%s]", count,
                     u->nick, u->username, u->hostname,
                     u->server->name);
        }
      }
    }
  }
  sendto_dcc(conn->fd, "End of .slist.");
  return 0;
}

static int d_droneent(struct Connection *conn, int parc, char *parv[])
{
#if defined(MONSERV) && defined(MON_DRONES)
  dlink_node *dl;
  struct DroneEntry *de;
  int count = 1;

  /* XXX - what permissions? */
  if (conn->myuser == NULL)
  {
    PERM_DENIED(conn, ".droneent");
    return 0;
  }

  DLINK_FOREACH(dl, MonServ.gecos_drone_list.head)
  {
    de = dl->data;
    sendto_dcc(conn->fd, "%d: gecos %s (%d hits)", count,
               de->name, de->hits);
    count++;
  }
  DLINK_FOREACH(dl, MonServ.version_drone_list.head)
  {
    de = dl->data;
    sendto_dcc(conn->fd, "%d: version %s (%d hits)", count,
               de->name, de->hits);
    count++;
  }
  DLINK_FOREACH(dl, MonServ.channel_drone_list.head)
  {
    de = dl->data;
    sendto_dcc(conn->fd, "%d: channel %s (%d hits)", count,
               de->name, de->hits);
    count++;
  }
  DLINK_FOREACH(dl, MonServ.username_drone_list.head)
  {
    de = dl->data;
    sendto_dcc(conn->fd, "%d: username %s (%d hits)", count,
               de->name, de->hits);
    count++;
  }
  DLINK_FOREACH(dl, MonServ.nick_drone_list.head)
  {
    de = dl->data;
    sendto_dcc(conn->fd, "%d: nick %s (%d hits)", count,
               de->name, de->hits);
    count++;
  }
  sendto_dcc(conn->fd, "End of list.");
#else

  sendto_dcc(conn->fd, ".droneent Disabled.");
#endif

  return 0;
}

static int d_mem(struct Connection *conn, int parc, char *parv[])
{
  if (conn->myuser == NULL || !IsSetOperAdmin(conn->myuser))
  {
    PERM_DENIED(conn, ".mem");
    return 0;
  }

  sendto_dcc(conn->fd, "Server count: %d memory: %d bytes",
             netinfo.servers, sizeof(struct Server) * netinfo.servers);
  sendto_dcc(conn->fd, "User count: %d memory: %d bytes", netinfo.users,
             sizeof(struct User) * netinfo.users);
  sendto_dcc(conn->fd, "dlink_node count: %d memory: %d bytes",
             dlink_count, dlink_count * sizeof(dlink_node));
  sendto_dcc(conn->fd, "MyUser count: %d memory: %d bytes",
             memcounts.myuser, memcounts.myuser * sizeof(struct MyUser));
  sendto_dcc(conn->fd, "Connection count: %d memory: %d bytes",
             memcounts.connection,
             memcounts.connection * sizeof(struct Connection));
  sendto_dcc(conn->fd, "Hidden count: %d memory: %d bytes",
             memcounts.hidden, memcounts.hidden * sizeof(struct Hidden));
#ifdef JUPESERV

  sendto_dcc(conn->fd, "Jupe count: %d memory: %d bytes", memcounts.jupe,
             memcounts.jupe * sizeof(struct Jupe));
  sendto_dcc(conn->fd, "JupeVote count: %d memory: %d bytes",
             memcounts.jupevote,
             memcounts.jupevote * sizeof(struct JupeVote));
#endif
#ifdef SPLITSERV

  sendto_dcc(conn->fd, "SpServer count: %d memory: %d bytes",
             memcounts.spserver,
             memcounts.spserver * sizeof(struct SpServer));
#endif

  sendto_dcc(conn->fd, "Stats count: %d memory: %d bytes",
             memcounts.stats, memcounts.stats * sizeof(struct Stats));
  sendto_dcc(conn->fd, "TLD count: %d memory: %d bytes", memcounts.tld,
             memcounts.tld * sizeof(struct TLD));
  sendto_dcc(conn->fd, "TLD->native count: %d memory: %d bytes",
             memcounts.native, memcounts.nativemem);
#ifdef TRACK_GECOS

  sendto_dcc(conn->fd, "User->gecos count: %d memory: %d bytes",
             memcounts.gecos, memcounts.gecosmem);
#endif
#ifdef MONSERV

  sendto_dcc(conn->fd, "HashEnt count: %d memory: %d bytes",
             memcounts.hashent, memcounts.hashent * sizeof(struct HashEnt));
  sendto_dcc(conn->fd, "ignore_server count: %d memory: %d bytes",
             memcounts.ignoreserver, memcounts.ignoreservermem);
#ifdef MON_DRONES

  sendto_dcc(conn->fd, "DroneEntry count: %d memory: %d bytes",
             memcounts.droneentry, memcounts.droneentry
             * sizeof(struct DroneEntry));
  sendto_dcc(conn->fd, "DroneNode count: %d memory: %d bytes",
             memcounts.dronenode, memcounts.dronenode
             * sizeof(struct DroneNode));
#endif
#ifdef TRACK_CTCPVERSION

  sendto_dcc(conn->fd, "User->version count: %d memory: %d bytes",
             memcounts.cversion, memcounts.cversionmem);
#endif
#endif

  sendto_dcc(conn->fd, "Channel count: %d memory: %d bytes",
             memcounts.channel, memcounts.channel * sizeof(struct Channel));
  sendto_dcc(conn->fd, "ChanUser count: %d memory: %d bytes",
             memcounts.chanuser,
             memcounts.chanuser * sizeof(struct ChanUser));
  sendto_dcc(conn->fd, "sendQ entry count: %d", memcounts.sendq);
#ifdef LISTSERV

  sendto_dcc(conn->fd, "topicinfo count: %d memory: %d bytes",
             memcounts.topicinfo, memcounts.topicinfomem);
  sendto_dcc(conn->fd, "topic count: %d memory: %d bytes",
             memcounts.topic, memcounts.topicmem);
#endif
  return 0;
}
